package com.zhang.learns.netty;

import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

public class NioSocketChannel {
    /**
     * 服务端
     */
    @Test
    public void test1() throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8080));
        // 设置serverSocketChannel的阻塞模式，默认是阻塞的，这里设置为非阻塞
        serverSocketChannel.configureBlocking(false);

        ByteBuffer byteBuffer = ByteBuffer.allocate(256);
        List<SocketChannel> socketChannelList = new ArrayList<>();

        while (true) {
            // 由于前面将serverSocketChannel的阻塞模式设置为非阻塞了，所以这里不会阻塞，如果没有连接返回的是null,程序会继续往下运行
            SocketChannel accept = serverSocketChannel.accept();

            if (!Objects.isNull(accept)) {
                socketChannelList.add(accept);
                //socketChannel也可以设置为非阻塞的模式
                accept.configureBlocking(false);
            }
            Iterator<SocketChannel> iterator = socketChannelList.iterator();
            while (iterator.hasNext()) {
                // 由于将socketChannel以设置为非阻塞模式，当没有读取到数据是返回0
                SocketChannel socketChannel = iterator.next();
                if (socketChannel.read(byteBuffer) > 0) {
                    //将byteBuffer切换为读的模式
                    byteBuffer.flip();
                    System.out.println("接受到客户端发送的消息：" + StandardCharsets.UTF_8.decode(byteBuffer).toString());
                    //清空byteBuffer让byteBuffer可以复用重新写入数据
                    byteBuffer.clear();
                } else if (socketChannel.read(byteBuffer) == -1) {
                    iterator.remove();
                }
            }
        }
    }

    /**
     * 客户端
     */
    @Test
    public void test2() throws IOException, InterruptedException {
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 8080));
        int i = 0;
        while (true) {
            ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode("我是客户" + i + " ");
            i += 1;
            socketChannel.write(byteBuffer);
            Thread.sleep(1000);
            if (i == 2) {
                break;
            }
        }
        socketChannel.close();
        System.out.println("客户端关闭");
    }


    // ***********************************************以上是没有selector选择器的*******************************

    /**
     * 基于选择器的 服务端
     */
    @Test
    public void test3() throws IOException {
        // 1 创建选择器对象
        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8081));
        // 设置serverSocketChannel的阻塞模式，默认是阻塞的，这里设置为非阻塞
        serverSocketChannel.configureBlocking(false);

        // 2 关联选择器和channel,并设置关注accept事件
        SelectionKey serverKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            // 3 没有事件发生就会让线程阻塞，当事件发生了就会停止阻塞,selector在有事件未处理时是不会阻塞的,事件发生后要么处理要么取消，不能置之不理
            selector.select();

            // 4 处理事件,返回一个集合，内部包含了所有的发生的事件
            Iterator<SelectionKey> selectionKeyIterator = selector.selectedKeys().iterator();
            while (selectionKeyIterator.hasNext()) {
                SelectionKey next = selectionKeyIterator.next();

                // 每处理完一个事件后需要将selectionKey从迭代器中删除
                selectionKeyIterator.remove();

                // 5 区分事件类型
                if (next.isAcceptable()) {
                    // 处理连接件事件
                    ServerSocketChannel channel = (ServerSocketChannel) next.channel();
                    SocketChannel socketChannel = channel.accept();
                    socketChannel.configureBlocking(false);
                    SelectionKey socketKey = socketChannel.register(selector, 0, null);
                    // 让socketChannel关注读事件
                    socketKey.interestOps(SelectionKey.OP_READ);
                    //给读事件绑定附件
                    ByteBuffer byteBuffer = ByteBuffer.allocate(10);
                    socketKey.attach(byteBuffer);
                } else if (next.isReadable()) {
                    // 处理读事件
                    SocketChannel socketChannel = (SocketChannel) next.channel();
                    try {
                        ByteBuffer byteBuffer = (ByteBuffer) next.attachment();
                        int readLen = socketChannel.read(byteBuffer);
                        if (readLen > 0) {
                            //将byteBuffer切换为读的模式
                            splite(byteBuffer);
                            //扩容
                            if (byteBuffer.position() == byteBuffer.limit()) {
                                ByteBuffer newByteffer = ByteBuffer.allocate(byteBuffer.capacity() * 2);
                                byteBuffer.flip();
                                newByteffer.put(byteBuffer);
                                next.attach(newByteffer);
                            }
                        } else if (readLen == -1) {
                            // 如果客户端强制中断连接，从selector集合中删除，取消事件处理
                            next.cancel();
                        }
                    } catch (Exception exception) {
                        exception.printStackTrace();
                        // 从selector集合中删除
                        next.cancel();
                    }

                }
            }
        }
    }

    /**
     * 客户端
     */
    @Test
    public void test4() throws IOException, InterruptedException {
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 8081));
        int i = 0;
        while (true) {
            ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode("xxxx\nxxxxxxx我是客户" + i + " \n我喜欢编程开发\n");
            i += 1;
            socketChannel.write(byteBuffer);
            Thread.sleep(1000);
            if (i == 2) {
                break;
            }
        }
        socketChannel.close();
        System.out.println("客户端关闭");
    }

    private static void splite(ByteBuffer source) {
        // byteBuffer切换为读模式
        source.flip();
        for (int i = 0; i < source.limit(); i++) {
            if (source.get(i) == '\n') {
                int len = i + 1 - source.position();
                ByteBuffer target = ByteBuffer.allocate(len);
                for (int j = 0; j < len; j++) {
                    target.put(source.get());
                }
                target.flip();
                System.out.println(StandardCharsets.UTF_8.decode(target).toString());
            }
        }
        // compact方法是整理压缩buffer,之后可以在这个buffer上继续写操作追加到后面
        source.compact();
    }

}
